package ihistoryApp.server;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import ihistoryApp.dal.entity.Answer;
import ihistoryApp.dal.entity.Message;
import ihistoryApp.dal.entity.Question;
import ihistoryApp.dal.entity.User;
import ihistoryApp.dal.mapper.AnswerMapper;
import ihistoryApp.dal.mapper.MessageMapper;
import ihistoryApp.dal.mapper.QuestionMapper;
import ihistoryApp.dal.mapper.UserMapper;
import ihistoryApp.enums.BattleRequestTypeEnum;
import ihistoryApp.enums.BattleResponseTypeEnum;
import ihistoryApp.request.BattleMakeAnswerRequest;
import ihistoryApp.request.BattleMakeAnswerResponse;
import ihistoryApp.request.BattleMatchRequest;
import ihistoryApp.request.BattleMatchSuccessResponse;
import ihistoryApp.utils.UserUtil;
import ihistoryApp.view.BattleRoom;
import ihistoryApp.view.NextQuestionView;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author 760042
 * @apiNote
 * @date 2024/5/25 17:14
 */
@ServerEndpoint("/websocket/battle/{uid}")
@Slf4j
@Component
public class BattleServer {
    private static ApplicationContext applicationContext;

    public static void setApplicationContext(ApplicationContext applicationContext) {
        BattleServer.applicationContext = applicationContext;
    }

    /**
     * 静态变量，用来记录当前在线连接数，线程安全的类。
     */
    private static AtomicInteger onlineSessionClientCount = new AtomicInteger(0);

    /**
     * 存放所有在线的客户端
     */
    private static Map<String, Session> onlineSessionClientMap = new ConcurrentHashMap<>();

    /**
     * 存放所有正在等待匹配的uid
     */
    private static Queue<String> waitingMatchQueue = new ConcurrentLinkedQueue<String>() {
    };

    /**
     * 所有正在对战的房间
     */
    private static Queue<BattleRoom> battleRoomList = new ConcurrentLinkedQueue<>();

    /**
     * 连接uid和连接会话
     */
    private String uid;
    private Session session;

    /**
     * 连接建立成功调用的方法。由前端<code>new WebSocket</code>触发
     *
     * @param uid     每次页面建立连接时传入到服务端的id，比如用户id等。可以自定义。
     * @param session 与某个客户端的连接会话，需要通过它来给客户端发送消息
     */
    @OnOpen
    public void onOpen(@PathParam("uid") String uid, Session session) {
        /**
         * session.getId()：当前session会话会自动生成一个id，从0开始累加的。
         */
        log.info("连接建立中 ==> session_id = {}， uid = {}", session.getId(), uid);
        //加入 Map中。将页面的uid和session绑定或者session.getId()与session
        //onlineSessionIdClientMap.put(session.getId(), session);
        onlineSessionClientMap.put(uid, session);

        //在线数加1
        onlineSessionClientCount.incrementAndGet();
        this.uid = uid;
        this.session = session;
//        sendToOne(uid, "连接成功");
        log.info("连接建立成功，当前在线数为：{} ==> 开始监听新连接：session_id = {}， uid = {}", onlineSessionClientMap.size(), session.getId(), uid);
    }

    /**
     * 连接关闭调用的方法。由前端<code>socket.close()</code>触发
     *
     * @param uid
     * @param session
     */
    @OnClose
    public void onClose(@PathParam("uid") String uid, Session session) {
        //onlineSessionIdClientMap.remove(session.getId());
        // 从 Map中移除
        onlineSessionClientMap.remove(uid);
        waitingMatchQueue.remove(uid);
        battleRoomList.remove(findRoom(uid));

        //在线数减1
        onlineSessionClientCount.decrementAndGet();
        log.info("连接关闭成功，当前在线数为：{} ==> 关闭该连接信息：session_id = {}， uid = {}, allUser:{}。", onlineSessionClientMap.size(), session.getId(), uid,
                JSON.toJSONString(onlineSessionClientMap.keySet()));
    }

    /**
     * 收到客户端消息后调用的方法。由前端<code>socket.send</code>触发
     * * 当服务端执行toSession.getAsyncRemote().sendText(xxx)后，前端的socket.onmessage得到监听。
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        /**
         * html界面传递来得数据格式，可以自定义.
         * {"uid":"user-1","message":"hello websocket"}
         */
        JSONObject jsonObject = JSON.parseObject(message);
//        String toUid = jsonObject.getString("uid");
//        String msg = jsonObject.getString("message");
        log.info("message:{}", message);
//        log.info("服务端收到客户端消息 ==> fromuid = {}, touid = {}, message = {}", uid, toUid, message);
        BattleMatchRequest requestType = JSONUtil.toBean(message, BattleMatchRequest.class);
        // 根据request类型进行操作
        if (BattleRequestTypeEnum.Match.equals(requestType.getBattleRequestType())) {
            match(uid);
        } else if(BattleRequestTypeEnum.Next_Question.equals(requestType.getBattleRequestType())) {
            nextQuestion(uid);
        } else if(BattleRequestTypeEnum.Answer.equals(requestType.getBattleRequestType())) {
            BattleMakeAnswerRequest request = JSONUtil.toBean(message, BattleMakeAnswerRequest.class);
            makeAnswer(uid, request);
        }

        /**
         * 模拟约定：如果未指定uid信息，则群发，否则就单独发送
         */
//            sendToOne(uid, message);
//            Message messageEntity = new Message();
//            messageEntity.setContent(message);
//            messageEntity.setSenderId(Long.parseLong(uid));
//            messageEntity.setReceiverId(Long.parseLong(uid));
//            messageEntity.setTimestamp(System.currentTimeMillis());
//            MessageMapper messageMapper = applicationContext.getBean(MessageMapper.class);
//            messageMapper.insert(messageEntity);

    }

    /**
     * 发生错误调用的方法
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket发生错误，错误信息为：" + error.getMessage());
        error.printStackTrace();
        onlineSessionClientCount.decrementAndGet();
        onlineSessionClientMap.remove(uid);
        waitingMatchQueue.remove(uid);
        battleRoomList.remove(findRoom(uid));
    }

    /**
     * 群发消息
     *
     * @param message 消息
     */
    private void sendToAll(String message) {
        // 遍历在线map集合
        onlineSessionClientMap.forEach((onlineuid, toSession) -> {
            // 排除掉自己
            if (!uid.equalsIgnoreCase(onlineuid)) {
                log.info("服务端给客户端群发消息 ==> uid = {}, touid = {}, message = {}", uid, onlineuid, message);
                toSession.getAsyncRemote().sendText(message);
            }
        });
    }

    /**
     * 指定发送消息
     *
     * @param toUid
     * @param message
     */
    private void sendToOne(String toUid, String message) {
        // 通过uid查询map中是否存在
        Session toSession = onlineSessionClientMap.get(toUid);
        if (toSession == null) {
            log.error("服务端给客户端发送消息 ==> touid = {} 不存在, message = {}", toUid, message);
            return;
        }
        // 异步发送
        log.info("服务端给客户端发送消息 ==> touid = {}, message = {}", toUid, message);
        toSession.getAsyncRemote().sendText(message);

        /*
        // 同步发送
        try {
            toSession.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("发送消息失败，WebSocket IO异常");
            e.printStackTrace();
        }*/
    }

    /**
     * 找房间
     */
    private BattleRoom findRoom(String uid) {
        log.info("uid:{}", uid);
        for (BattleRoom battleRoom: battleRoomList) {
            log.info("uid:{}, uidA:{}, uidB:{}", uid, battleRoom.getUidA(), battleRoom.getUidB());
            if (battleRoom.getUidA().equals(uid) || battleRoom.getUidB().equals(uid)) {
                return battleRoom;
            }
        }
        return null;
    }

    /**
     * 匹配
     * @param uid 用户id
     */
    private void match(String uid) {
        // 看当前有没有正在匹配的
        if (CollectionUtil.isEmpty(waitingMatchQueue)) {
            waitingMatchQueue.add(uid);
            return;
        }
        if (waitingMatchQueue.contains(uid) && waitingMatchQueue.size() == 1) {
            return;
        }
        // 查题目列表的id
        LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<Question>()
                .select(List.of(Question::getQuestionId))
                ;
        QuestionMapper questionMapper = applicationContext.getBean(QuestionMapper.class);
        List<Long> questionIdList = questionMapper.selectList(queryWrapper).stream().map(Question::getQuestionId).toList();
        log.info("requestionIdList:{}", JSON.toJSONString(questionIdList));
        // 随机选题目
        questionIdList = RandomUtil.randomEleList(questionIdList, 8);
        queryWrapper = new LambdaQueryWrapper<Question>()
                .in(Question::getQuestionId, questionIdList)
                ;
        List<Question> questionList = questionMapper.selectList(queryWrapper);
        log.info("final_requestionIdList:{}", JSON.toJSONString(questionList));
        // 拿到等待中的用户
        String uidB = waitingMatchQueue.poll();
        // 房间初始化
        BattleRoom battleRoom = BattleRoom.builder()
                .uidA(uid)
                .uidB(uidB)
                .questionList(questionList)
                .nowIndex(-1)
                .scoreB(0L)
                .scoreA(0L)
                .status(new AtomicInteger(1))
                .build();
        battleRoomList.add(battleRoom);
        // 匹配成功，进行处理
        matchSuccess(uid, uidB);
    }

    /**
     * 匹配完成
     */
    private void matchSuccess(String uidA, String uidB) {
        UserMapper userMapper = applicationContext.getBean(UserMapper.class);
        User userB = userMapper.selectById(uidB);
        User userA = userMapper.selectById(uidA);
        BattleMatchSuccessResponse responseA = new BattleMatchSuccessResponse();
        BattleMatchSuccessResponse responseB = new BattleMatchSuccessResponse();
        responseA.setBattleResponseType(BattleResponseTypeEnum.Match_Success);
        responseB.setBattleResponseType(BattleResponseTypeEnum.Match_Success);
        responseA.setNickName(userA.getNickName());
        responseB.setNickName(userB.getNickName());
        responseA.setUid(userA.getUid());
        responseB.setUid(userB.getUid());
        // 向用户发送匹配成功消息
        sendToOne(uidA, JSON.toJSONString(responseB));
        sendToOne(uidB, JSON.toJSONString(responseA));
    }

    /**
     * 下一题
     */
    private void nextQuestion(String uid) {
        BattleRoom nowRoom = findRoom(uid);
        nowRoom.getStatus().incrementAndGet();
        if (nowRoom.getStatus().get() == 2) {
            NextQuestionView questionView = new NextQuestionView();
            questionView.setBattleResponseType(BattleResponseTypeEnum.Next_Question);
            questionView.setQuestion(nowRoom.nextQuestion());
            log.info("当前题目:{}", JSON.toJSONString(questionView.getQuestion()));
            if (questionView.getQuestion() == null) {
                nowRoom.setStatus(new AtomicInteger(0));
                return;
            }
            LambdaQueryWrapper<Answer> answerLambdaQueryWrapper = new LambdaQueryWrapper<Answer>()
                    .eq(Answer::getQuestionId, questionView.getQuestion().getQuestionId())
                    ;
            AnswerMapper answerMapper = applicationContext.getBean(AnswerMapper.class);
            List<Answer> answerList = answerMapper.selectList(answerLambdaQueryWrapper);
            questionView.setAnswerList(answerList);
            sendToOne(nowRoom.getUidA(), JSON.toJSONString(questionView));
            sendToOne(nowRoom.getUidB(), JSON.toJSONString(questionView));
            nowRoom.setStatus(new AtomicInteger(0));
        }




    }

    /**
     * 作答
     */
    private void makeAnswer(String uid, BattleMakeAnswerRequest request) {
        BattleRoom battleRoom = findRoom(uid);
        if (battleRoom == null) {
            return;
        }
        // 查答案
        Question nowQuestion = battleRoom.getNowQuestion();
        AnswerMapper answerMapper = applicationContext.getBean(AnswerMapper.class);
        Answer answer = answerMapper.selectOne(new LambdaQueryWrapper<Answer>()
                .eq(Answer::getQuestionId, nowQuestion.getQuestionId())
                .eq(Answer::getIsCorrect, true));
        log.info("choose_answer:{}, correct_answer:{}", request.getAnswerId(), answer.getAnswerId());
        // 看答案对不对
        log.info("作答_statsu:{}", battleRoom.getStatus().get());
        if (request.getAnswerId().equals(answer.getAnswerId())) {
            makeAnswerResponse(uid, battleRoom.calScore());
        } else {
            makeAnswerResponse(battleRoom.getUidA().equals(uid) ? battleRoom.getUidB() : uid, battleRoom.calScore());
        }
    }

    /**
     * 作答结果
     */
    private void makeAnswerResponse(String uid, Long score) {
        BattleRoom battleRoom = findRoom(uid);
        if (battleRoom == null) {
            log.error("room is null");
            return;
        }
        battleRoom.addScore(uid, score);
        log.info("作答完成，uid:{}, score:{}, room:{}", uid, score, JSONUtil.toJsonStr(battleRoom));
        BattleMakeAnswerResponse request = new BattleMakeAnswerResponse();
        request.setBattleResponseType(BattleResponseTypeEnum.Make_Answer);
        request.setBattleRoom(battleRoom);
        // 发送作答结束请求
        sendToOne(battleRoom.getUidA(), JSON.toJSONString(request));
        sendToOne(battleRoom.getUidB(), JSON.toJSONString(request));
        // 如果当前是最后一题
        if (battleRoom.isLastQuestion()) {
            log.info("最后一题，答题结束");
            if (battleRoom.getScoreA() > battleRoom.getScoreB()) {
                complete(battleRoom.getUidA(), 100, 100);
                complete(battleRoom.getUidB(), 50, 50);
            } else {
                complete(battleRoom.getUidB(), 100, 100);
                complete(battleRoom.getUidA(), 50, 50);
            }
        }
    }


    /**
     * 比赛结束
     */
    private void complete(String uid, long exp, long score) {
        BattleRoom room = findRoom(uid);
        UserUtil userUtil = applicationContext.getBean(UserUtil.class);
        userUtil.addExp(Long.valueOf(uid), exp);
        userUtil.addScore(Long.parseLong(uid), score);
        BattleMakeAnswerResponse response = new BattleMakeAnswerResponse();
        response.setBattleResponseType(BattleResponseTypeEnum.Complete);
        if (room.getUidA().equals(uid)) {
            room.setCompleteScoreA(score);
            room.setExpA(exp);
        } else if(room.getUidB().equals(uid)) {
            room.setCompleteScoreB(score);
            room.setExpB(exp);
        }
        response.setBattleRoom(room);
        sendToOne(uid, JSON.toJSONString(response));
        room.getStatus().incrementAndGet();
        log.info("room_status:{}", room.getStatus().get());
        if (room.getStatus().get() == 2) {
//            battleRoomList.remove(room);
        }
    }
}
